﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PSE.Updater.Common.Gateway.DataContract;
using PSE.Updater.Common.Providers;
using PSE.Updater.Client.Engine;
using PSE.Updater.Common;
using PSE.Framework.ErrorLogging;
using System.Timers;
using PSE.Updater.Client.UI;
using Microsoft.Win32;

namespace PSE.Updater.Client.Providers.Updates
{
    public class Update : UpdateBase
    {
        private enum ExecuteStatusEnum
        {
            Installed,
            NotInstalled,
            Error
        }

        private const int CONST_DEFAULT_HOUR = 1;
        private const string CONST_REG_VALUE_NAME = "PSE.Updater.Application";

        public Update() : base(ProviderType.Updates) { }

        private List<ModuleUpdatePackage> _modulesUpdatePackage = null;

        protected override void ShowUpdates(List<ModuleUpdatePackage> modules)
        {
            //System.Threading.Thread.Sleep(6.SecondsToMilliseconds());

            _modulesUpdatePackage = modules;
            ShowSytrayIcon("BM&FBOVESPA", "Existem Atualizações disponíveis.", () => BeginInstallation());
        }

        private void BeginInstallation()
        {
            Default geral = new Default();
            geral.Screen = Default.ScreenType.Initialize;
            geral.ModulesUpdatePackage = _modulesUpdatePackage;
            geral.OnInstallNowClick = () =>
            {
                geral.Close();
                Default screen = new Default();
                screen.OnFinalizeClick = () => screen.Close();

                ShowSytrayIcon("BM&FBOVESPA", "Os pacotes serão instalados", null);

                var downloadedModules = this.BeginDownload(_modulesUpdatePackage);

                InstallNow(downloadedModules);
                IList<ModuleUpdatePackage> installedModules = downloadedModules.Select(d => d.ModuleUpdatePackage).ToList();
                if (installedModules == null)
                {
                    screen.Screen = Default.ScreenType.InstallError;
                }
                else
                {
                    if (installedModules.Count(m => m.Status != PackageStatus.Installed) > 0)
                        screen.Screen = Default.ScreenType.InstallError;
                    else
                        screen.Screen = Default.ScreenType.Finalize;
                }

                screen.ModulesUpdatePackage = installedModules;
                screen.ShowDialog();

                RemoveUpdateOnWinStart();
            };

            geral.OnPostponeClick = () =>
            {
                Postpone(CONST_DEFAULT_HOUR);
                geral.Close();
            };

            geral.OnScheduleUpdateClick = (hour, minutes, installOnWinStart) =>
            {
                ScheduleUpdate(hour, minutes, installOnWinStart);
                geral.Close();
            };
            geral.ShowDialog();

            if (this.OnClose != null)
            {
                HideSytrayIcon();
                OnClose();
            }
        }

        private void InstallNow(IList<DownloadedFile> downloadedModules)
        {
            var groups = from n in downloadedModules
                         group n by n.ModuleUpdatePackage.ModuleName into g
                         select new { Module = g.Key, Packages = g };


            foreach (var group in groups)
            {
                for (int i = 0; i < group.Packages.Count(); i++)
                {
                    var package = group.Packages.ElementAt(i);

                    string message;
                    ExecuteStatusEnum status = ExecutePackage(package, out message);
                    ModuleUpdatePackage moduleUpdatePackage = _modulesUpdatePackage.First(m => m == package.ModuleUpdatePackage);
                    moduleUpdatePackage.UpdateDescription = message;
                    switch (status)
                    {
                        case ExecuteStatusEnum.Installed:
                            moduleUpdatePackage.Status = PackageStatus.Installed;
                            break;
                        case ExecuteStatusEnum.NotInstalled:
                            moduleUpdatePackage.Status = PackageStatus.Invalid;
                            break;
                        case ExecuteStatusEnum.Error:
                            moduleUpdatePackage.Status = PackageStatus.Missing;
                            break;
                    }

                    if (status != ExecuteStatusEnum.Installed)
                    {
                        for (int j = i + 1; j < group.Packages.Count(); j++)
                        {
                            var invalidPackage = group.Packages.ElementAt(j);

                            invalidPackage.ModuleUpdatePackage.UpdateDescription = string.Format("Quebra na sequência de instalação dos pacotes...\n{0}", message);
                            invalidPackage.ModuleUpdatePackage.Status = PackageStatus.Invalid;
                        }
                        break;
                    }
                }
            }
            ScheduleExecutation.Instance.Remove();
        }

        private ExecuteStatusEnum ExecutePackage(DownloadedFile downloadedFile, out string message)
        {
            message = string.Empty;

            if (!string.IsNullOrEmpty(downloadedFile.Path))
            {
                var exitCode = Util.Instance.ExecuteSystem(downloadedFile.Path, "/hidewizard", true);
                switch (exitCode)
                {
                    case 0:
                        if (IsInstalled(downloadedFile.ModuleUpdatePackage))
                        {
                            return ExecuteStatusEnum.Installed;
                        }
                        else
                        {
                            message = "Não foi possível instalar o pacote...";
                            return ExecuteStatusEnum.NotInstalled;
                        }
                    case -1:
                    case -2:
                        {
                            message = string.Format("Erro inesperado {0}", exitCode);
                            return ExecuteStatusEnum.Error;
                        }
                }
            }
            else
            {
                string fileName = string.Empty;
                if (downloadedFile.ModuleUpdatePackage != null)
                    fileName = downloadedFile.ModuleUpdatePackage.FileName;

                message = string.Format("Possivelmente o arquivo {0} não foi encontrado no servidor", fileName);
                ErrorLogger.WriteLog(LogType.Error, message);

                return ExecuteStatusEnum.Error;
            }
            return ExecuteStatusEnum.NotInstalled;
        }

        protected override List<ModuleUpdatePackage> GetAvailableUpdates(SystemInformation system)
        {
            List<ModuleUpdatePackage> avaliableDependenceModules = null;
            List<ModuleUpdatePackage> avaliableModules = null;

            if (system.SystemDetails.Modules == null)
                return null;

            if (system.SystemDetails.Modules.Count > 0)
            {
                try
                {
                    avaliableModules = Service.Proxy.Instance.GetAvailableUpdates(ProviderType.Updates, ConvertToModulesDataContract(system.SystemName, system.SystemDetails.Modules));
                    if (avaliableModules != null)
                        avaliableModules = avaliableModules.Distinct().ToList();

                }
                catch (Exception ex)
                {
                    ErrorLogger.WriteLog(LogType.Error, ex);
                    return null;
                }

                if (avaliableDependenceModules == null)
                    avaliableDependenceModules = avaliableModules;
                else
                    if (avaliableModules != null)
                        avaliableDependenceModules.AddRange(avaliableModules);
            }
            return avaliableDependenceModules;
        }

        private List<ModuleVersion> ConvertToDependenceModulesDataContract(List<SystemDetails> dependencies)
        {
            List<ModuleVersion> modulesDataContract = new List<ModuleVersion>();

            foreach (var dependence in dependencies)
                if (dependence.Modules != null)
                    foreach (var module in dependence.Modules)
                        modulesDataContract.Add(new ModuleVersion
                        {
                            SystemName = dependence.SystemName,
                            ModuleName = module.ModuleName,
                            Version = module.Version
                        });

            return modulesDataContract;
        }

        protected override void DownloadProgressChanged(ModuleUpdatePackage pkg, int progress)
        {
            //TODO

        }

        private bool IsInstalled(ModuleUpdatePackage update)
        {
            try
            {
                bool isInstalled = true;

                string[] modules = update.ModuleName.Split(';');
                foreach (var module in modules)
                {
                    if (!string.IsNullOrEmpty(module))
                    {
                        SystemDetails system = new SystemDetails();

                        string[] versions = update.Version.Split('.');
                        int majorVersion = Convert.ToInt32(versions[0]);

                        ModuleDetails newVersion = system.GetModuleVersion(update.SystemName, majorVersion, module);

                        if (update.Version != newVersion.Version)
                        {
                            isInstalled = false;
                            break;
                        }
                    }
                }
                return isInstalled;
            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, ex);
                return false;
            }
        }

        private void ScheduleUpdate(int hours, int minutes, bool updateOnWinStart)
        {
            if (updateOnWinStart)
                SetUpdateOnWinStart();

            DateTime? scheduleDate = null;
            if (DateTime.Now.Hour > hours)
            {
                //Tomorow
                DateTime nextDate = DateTime.Now.AddDays(1);
                scheduleDate = new DateTime(nextDate.Year, nextDate.Month, nextDate.Day, hours, minutes, 0);
            }
            else
            {
                scheduleDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, hours, minutes, 0);
            }

            if (scheduleDate == null)
            {
                Exception ex = new Exception("Cannot calculate de Schedule Date");
                ErrorLogger.WriteLog(LogType.Error, ex);
                //throw ex;
            }

            ScheduleExecutation.Instance.Add(scheduleDate.Value);
        }

        private void Postpone(int hour)
        {
            var dateTime = DateTime.Now.AddHours(hour);
            ScheduleExecutation.Instance.Add(dateTime);
        }

        private void SetUpdateOnWinStart()
        {
            ScheduleExecutation.Instance.SetUpdateOnWinStart();
        }

        private void RemoveUpdateOnWinStart()
        {
            ScheduleExecutation.Instance.RemoveUpdateOnWinStart();
        }
    }
}